package com.jerome.fs.json;

import com.jerome.fs.constants.AppConstant;
import com.jerome.fs.util.ReflectUtil;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

/**
 * JSON Serializer
 * Currently, it only supports some common data types.
 * @author Jerome
 * @date 2023/9/9
 */
public class JsonSerializer {

    /**
     * Serialize objects differently based on the input object type.
     * @param object The object to be serialized
     * @return The serialized JSON string
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public static String serialize(Object object) throws InvocationTargetException, IllegalAccessException {
        if (object == null) {
            return "null";
        } else if (object instanceof String) {
            return "\"" + escapeString((String) object) + "\"";
        } else if (object instanceof Number || object instanceof Boolean) {
            return object.toString();
        } else if (object instanceof List) {
            return serializeList((List<?>) object);
        } else if (object instanceof Map) {
            return serializeMap((Map<?, ?>) object);
        } else {
            String s = object.toString();
            // Check if it's a numeric type
            if (s.matches(AppConstant.NUMBER_REGEX)){
                return s;
            }
            return serializeObject(object);
        }
    }

    /**
     * Serialize entity objects.
     * @param object The entity object
     * @return The serialized JSON string
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    private static String serializeObject(Object object) throws InvocationTargetException, IllegalAccessException {
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        Map<String, Method> getterMethodMap = ReflectUtil.getGetter(object.getClass());
        int count = 0;
        for (Map.Entry<String, Method> entry : getterMethodMap.entrySet()) {
            if (count > 0) {
                sb.append(",");
            }
            sb.append("\"").append(escapeString(entry.getKey())).append("\":");
            Object invoke = entry.getValue().invoke(object);
            sb.append(serialize(invoke));
            count++;
        }
        sb.append("}");
        return sb.toString();
    }

    /**
     * Serialize lists (arrays).
     * @param list The list to be serialized
     * @return The serialized JSON string
     */
    private static String serializeList(List<?> list) throws InvocationTargetException, IllegalAccessException {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (int i = 0; i < list.size(); i++) {
            if (i > 0) {
                sb.append(",");
            }
            sb.append(serialize(list.get(i)));
        }
        sb.append("]");
        return sb.toString();
    }

    /**
     * Serialize maps.
     * @param map The map to be serialized
     * @return The serialized JSON string
     */
    private static String serializeMap(Map<?, ?> map) throws InvocationTargetException, IllegalAccessException {
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        int count = 0;
        for (Map.Entry<?, ?> entry : map.entrySet()) {
            if (count > 0) {
                sb.append(",");
            }
            sb.append("\"").append(escapeString(entry.getKey().toString())).append("\":");
            sb.append(serialize(entry.getValue()));
            count++;
        }
        sb.append("}");
        return sb.toString();
    }

    /**
     * Handle escaping special characters in strings.
     * @param input The input string
     * @return The escaped string
     */
    private static String escapeString(String input) {
        StringBuilder sb = new StringBuilder();
        for (char c : input.toCharArray()) {
            if (c == '"') {
                sb.append("\\\"");
            } else if (c == '\\') {
                sb.append("\\\\");
            } else if (c == '\n') {
                sb.append("\\n");
            } else if (c == '\r') {
                sb.append("\\r");
            } else if (c == '\t') {
                sb.append("\\t");
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }
}
